﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.Threading;
using System.Security.Authentication;
using NetFrameworkExtensions.Enterprise.Properties;

namespace NetFrameworkExtensions.Enterprise.Security
{
  [Serializable]
  public class PrincipalBase<TPrincipal, TIdentity> :
    IPrincipalBase
    where TPrincipal : PrincipalBase<TPrincipal, TIdentity>
    where TIdentity : class, IIdentity, new()
  {
    TIdentity identity;
    HashSet<string> roles;

    public PrincipalBase(TIdentity identity, IEnumerable<string> roles = null)
    {
      if (identity == null)
        throw new ArgumentNullException("identity");
      if (!identity.IsAuthenticated && roles != null && roles.Any())
        throw new NotSupportedException(FormattedResources.UnauthorizedUserCannotHaveAnyRole);

      this.identity = identity;
      this.roles = roles == null ? null : new HashSet<string>(roles);
    }

    public PrincipalBase(TIdentity identity, params string[] roles)
      : this(identity, (IEnumerable<string>)roles)
    {
    }

    public PrincipalBase()
      : this(new TIdentity())
    {
    }

    public TIdentity Identity
    {
      get { return identity; }
    }

    IIdentity IPrincipal.Identity
    {
      get { return Identity; }
    }

    public bool IsInRole(string role)
    {
      if (role == null) throw new ArgumentNullException("role");
      if (!identity.IsAuthenticated) return false;
      if (roles == null) return false;
      return roles.Contains(role);
    }

    public void CheckIsInRole(string role)
    {
      if (!IsInRole(role))
      {
        string message = FormatRoleAuthenticationMessage(role);
        throw new AuthenticationException(message);
      }
    }

    protected virtual string FormatRoleAuthenticationMessage(string role)
    {
      return FormattedResources.FormatRoleAuthenticationMessage(role);
    }

    public override bool Equals(object obj)
    {
      var other = obj as TPrincipal;
      return other != null && identity.Equals(other.identity);
    }

    public override int GetHashCode()
    {
      return identity.GetHashCode();
    }

    public override string ToString()
    {
      return String.Format("Principal of {0}", identity);
    }

    static public TPrincipal Current
    {
      get
      {
        var principal = Thread.CurrentPrincipal as TPrincipal;
        return principal;
      }
    }

    static public TIdentity CurrentIdentity
    {
      get
      {
        var principal = Thread.CurrentPrincipal as TPrincipal;
        if (principal == null) return null;
        return principal.Identity;
      }
    }

    static public bool CurrentIsAuthenticated
    {
      get 
      {
        var principal = Current;
        return principal != null && principal.Identity.IsAuthenticated; 
      }
    }
  }
}
